/**
 * Copyright (C) 2012 The SkyTvOS Project
 * <p/>
 * Version     Date           Author
 * ─────────────────────────────────────
 * 2014年11月3日         Root.Lu
 */

package com.coocaa.x.service.litedownloader.downloader.http;

import com.coocaa.x.framework.utils.CacheTool;
import com.coocaa.x.framework.utils.LogUtils;
import com.coocaa.x.framework.utils.Storage;
import com.coocaa.x.service.litedownloader.data.DownloadError;
import com.coocaa.x.service.litedownloader.data.DownloadTask;
import com.coocaa.x.service.litedownloader.data.TaskInfo;
import com.coocaa.x.service.litedownloader.data.http.HttpDownloaderInfo;
import com.coocaa.x.service.litedownloader.db.DownloaderDB;
import com.coocaa.x.service.litedownloader.db.DownloaderTable;
import com.coocaa.x.service.litedownloader.downloader.Downloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

public class HttpDownloader extends Downloader {
    private static HttpDownloaderTable table = null;
    private boolean bStop = false, bDelete = false;
    private String savefile = null, tempfile = null;
    private HttpDownloaderInfo downloaderInfo = null;

    private HttpURLConnection httpUrl = null;
    private InputStream input = null;
    private RandomAccessFile oSavedFile = null;

    public HttpDownloader() {
        try {
            table = (HttpDownloaderTable) DownloaderDB.getInstance().getTable(
                    HttpDownloaderTable.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private long getFileSize(String url) throws Exception {
        int nFileLength = -1;
        while (nFileLength < 0) {
            HttpURLConnection httpUrl = null;
            try {
                URL _url = new URL(url);
                httpUrl = (HttpURLConnection) _url.openConnection();

                httpUrl.setReadTimeout(3000);
                httpUrl.setConnectTimeout(3000);
                httpUrl.setRequestProperty("User-Agent", "NetFox");
                // int responseCode = httpUrl.getResponseCode();
                // if (responseCode >= 400)
                // {
                // nFileLength = -2; // -2 represent access is error
                // } else
                // {
                String sHeader;
                for (int i = 1; ; i++) {
                    sHeader = httpUrl.getHeaderFieldKey(i);
                    if (sHeader != null) {
                        if (sHeader.equals("Content-Length")) {
                            nFileLength = Integer.parseInt(httpUrl.getHeaderField(sHeader));
                            break;
                        }
                    } else
                        break;
                }
                // }
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            } finally {
                if (httpUrl != null)
                    httpUrl.disconnect();
                httpUrl = null;
            }
        }
        return nFileLength;
    }

    private void createTempFile(String path, long length) {
        if (!(length > 0))
            return;
        try {
            FileOutputStream out = new FileOutputStream(new File(path));
            byte[] buffer = null;
            int bytewrite = 0;
            do {
                if (bytewrite == length)
                    break;
                if (((length - bytewrite) / 4096) > 0)
                    buffer = new byte[4096];
                else
                    buffer = new byte[(int) ((length - bytewrite) % 4096)];
                bytewrite += buffer.length;
                out.write(buffer);
            } while (true);
            out.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private int checksum(long length) {
        // TODO Auto-generated method stub
        int ret = 0;
        Exception e = null;
        try {
            // InputStream in = new FileInputStream(tempfile);
            InputStream in = new FileInputStream(savefile);
            byte[] tempbytes = null;
            long byteread = 0, totalread = 0;
            do {
                if (totalread == length)
                    break;
                if (tempbytes != null) {
                    for (byte b : tempbytes)
                        ret += b;
                }
                totalread += byteread;
                if ((length - totalread) / 4096 > 0)
                    tempbytes = new byte[4096];
                else
                    tempbytes = new byte[(int) ((length - totalread) % 4096)];
            } while ((byteread = in.read(tempbytes)) != -1);
            in.close();
        } catch (FileNotFoundException ee) {
            // TODO Auto-generated catch block
            e = ee;
        } catch (IOException ee) {
            // TODO Auto-generated catch block
            e = ee;
        } catch (Exception ee) {
            e = ee;
        }
        if (e != null) {
            e.printStackTrace();
            LogUtils.e("httpdownloader checksum error:" + e.getLocalizedMessage());
        }
        return ret;
    }

    @Override
    protected TaskInfo _prepare() throws OnErrorException {
        DownloadTask task = getDownloadTask();
        if (table != null)
            downloaderInfo = table.get(task.ID);
        LogUtils.d("httpdownloader _prepare:" + downloaderInfo);
        if (downloaderInfo == null) {
            downloaderInfo = new HttpDownloaderInfo();
            if (table != null)
                table.add(task.ID, downloaderInfo);
        }
        if (downloaderInfo.length <= 0)
            try {
                downloaderInfo.length = getFileSize(task.url);
            } catch (Exception e) {
                DownloadError error = new DownloadError();
                error.errcode = -10;
                error.error = e.toString();
                throw new OnErrorException(error);
            }
        TaskInfo info = new TaskInfo();
        info.length = downloaderInfo.length;
        if (task.savepath == null || task.savepath.equals(""))
            updateSavePath(task, info.length);
        else {
            //判断当前路径下空间是否足够，如果不足则换别的路径, 实在没有可下载路径则抛异常
            LogUtils.i("xfk", "check task.savepath: " + task.savepath);
            boolean isEnough = checkCurrentSpaceIsEnough(task, downloaderInfo.length);
            LogUtils.i("xfk", "isEnough: " + isEnough);

            if (!isEnough) {
                String newPath = getSavePath(task, downloaderInfo.length);
                if (newPath != null) {
                    savefile = task.savepath + "/" + task.filename;
                    task.savepath = newPath;
                    File file = new File(savefile);
                    if (file.exists()) {
                        file.delete();
                    }
                    downloaderInfo.current = 0;
                    downloaderInfo.checksum = 0;
                } else {
                    LogUtils.i("xfk", "not enough");
                    DownloadError error = new DownloadError();
                    error.errcode = DownloadError.ERRCODE_NO_DISK_SPACE;
                    error.error = "savepath:" + task.savepath + " filelength:" + info.length;
                    throw new OnErrorException(error);
                }

            } else {
                savefile = task.savepath + "/" + task.filename;
            }
        }

        //File file = new File(tempfile);
        File file = new File(savefile);

        if (file != null && file.exists()) {
            int checksum = checksum(downloaderInfo.current);
            LogUtils.d("httpdownloader checksum " + checksum + "/" + downloaderInfo.checksum);
            if (checksum != downloaderInfo.checksum) {
                LogUtils.d("httpdownloader _prepare checksum not equal start from 0!!");
                downloaderInfo.checksum = 0;
                downloaderInfo.current = 0;
            } else {
                info.current = downloaderInfo.current;
            }
        } else {
            task.savepath = null;
            updateSavePath(task, info.length);
            //Storage.setReservedMemory(info.length);
            //createTempFile(tempfile, info.length);
            downloaderInfo.checksum = 0;
            downloaderInfo.current = 0;
        }
        return info;
    }

    private boolean checkCurrentSpaceIsEnough(DownloadTask task, long length) {
        long needSpace = 0;
        DownloaderTable dtable = null;
        CacheTool<String, DownloadTask> taskcache = null;
        HttpDownloaderTable table = null;
        String path = task.savepath;
        long configSize = Storage.getConfigSize();
        long totalSpace = Storage.getTotalSpace(path, false);
        dtable = (DownloaderTable) DownloaderDB.getInstance().getTable(DownloaderTable.class);
        taskcache = new CacheTool<String, DownloadTask>(dtable);
        taskcache.flush();

        try {
            table = (HttpDownloaderTable) DownloaderDB.getInstance().getTable(
                    HttpDownloaderTable.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (taskcache == null || table == null) {
            needSpace = downloaderInfo.length - downloaderInfo.current;
            return Storage.checkNewTaskCanDownLoad(path, needSpace, configSize, totalSpace);
        }

        List<DownloadTask> list = taskcache.values();
        for (DownloadTask tmpTask : list) {
            if (tmpTask.savepath != null && tmpTask.savepath.equals(path)) {
                HttpDownloaderInfo downloaderInfo = table.get(task.ID);
                if (downloaderInfo != null) {
                    needSpace += downloaderInfo.length - downloaderInfo.current;
                }
            }
        }

        return Storage.checkNewTaskCanDownLoad(path, needSpace, configSize, totalSpace);
    }

    private void updateSavePath(DownloadTask task, long length) throws OnErrorException {
        if (task.savepath == null)
            task.savepath = getSavePath(task, length);
        else if (!task.savepath.equals(""))
            Storage.refreshInternalMemoryPath();

        if (task.savepath == null) {
            task.savepath = "";
            DownloadError error = new DownloadError();
            error.errcode = DownloadError.ERRCODE_NO_DISK_SPACE;
            error.error = "savepath:" + task.savepath + " filelength:" + length;
            throw new OnErrorException(error);
        }


        savefile = task.savepath + "/" + task.filename;
        LogUtils.d("%%%%%%%%%%%%%%%%%%%%%%%%%% used savepath:" + savefile);
        //tempfile = savefile + ".cc";
    }


    @Override
    protected void _process() throws OnStopException, OnDeleteException, OnErrorException {
        // TODO Auto-generated method stub
        DownloadTask task = null;
        boolean isDelete = false;

        try {
            task = getDownloadTask();
            URL _url = new URL(task.url);
            // oSavedFile = new RandomAccessFile(tempfile, "rw");
            oSavedFile = new RandomAccessFile(savefile, "rw");
            oSavedFile.seek(downloaderInfo.current);
            LogUtils.d("httpdownloader pointer:" + oSavedFile.getFilePointer() + "   current:"
                    + downloaderInfo.current);
            httpUrl = (HttpURLConnection) _url.openConnection();
            if (downloaderInfo.current > 0)
                httpUrl.setRequestProperty("RANGE", "bytes=" + downloaderInfo.current + "-");
            input = httpUrl.getInputStream();
            byte[] b = new byte[1024];
            int nRead;
            int update_info_delay = 0;
            int update_rm_delay = 0;
            long totalSpace = 0;
            long configSize = 0;

            totalSpace = Storage.getTotalSpace(task.savepath, true);
            configSize = Storage.getConfigSize();
            LogUtils.i("HttpDownloader", "savepath: " + task.savepath);
            LogUtils.i("HttpDownloader", "totalSpace: " + totalSpace + ", configSize: " + configSize);
            //剩余内部空间小于100M抛异常
            if (!Storage.isHasEnoughSpaceToDownload(task.savepath, configSize, totalSpace)) {
                DownloadError error = new DownloadError();
                error.errcode = DownloadError.ERRCODE_NO_DISK_SPACE;
                error.error = "savepath:" + task.savepath + " filelength:" + downloaderInfo.length;
                throw new OnErrorException(error);
            }

            while ((nRead = input.read(b)) > 0) {
                if (bStop) {
                    throw new OnStopException();
                }

                if (bDelete) {
                    if (savefile != null) {
                        File df = new File(savefile);
                        if (df.exists()) {
                            df.delete();
                        }
                    }

                    isDelete = true;
                    throw new OnDeleteException();
                }

                oSavedFile.write(b, 0, nRead);
                downloaderInfo.current += nRead;

                for (int i = 0; i < nRead; i++)
                    downloaderInfo.checksum += b[i];
                if (update_info_delay == 100) {
                    if (table != null)
                        table.update(task.ID, downloaderInfo);
                    update_info_delay = 0;
                }
                update_info_delay++;

                if (update_rm_delay == 1000) {
                    update_rm_delay = 0;
                    if (!Storage.isHasEnoughSpaceToDownload(task.savepath, configSize, totalSpace)) {
                        //剩余空间小于十分之一抛异常
                        LogUtils.i("FreeSpace Less Than One Tenth");
                        DownloadError error = new DownloadError();
                        error.errcode = DownloadError.ERRCODE_NO_DISK_SPACE;
                        error.error = "savepath:" + task.savepath + " fileslength:" + downloaderInfo.length;
                        throw new OnErrorException(error);
                    }
                } else {
                    update_rm_delay++;
                }
            }
            //File df = new File(tempfile);
            //df.renameTo(new File(savefile));
        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            DownloadError error = new DownloadError();
            error.error = e.getMessage();
            throw new OnErrorException(error);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            DownloadError error = new DownloadError();
            error.error = e.getMessage();
            throw new OnErrorException(error);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            DownloadError error = new DownloadError();
            error.error = e.getMessage();
            throw new OnErrorException(error);
        } finally {
            if (!isDelete)
                try {
                    table.update(task.ID, downloaderInfo);
                } catch (Exception e) {
                    e.printStackTrace();
                }
        }

    }

    @Override
    protected float _current() {
        // TODO Auto-generated method stub
        return downloaderInfo.current;
    }

    @Override
    protected void _start() {
        // TODO Auto-generated method stub
        bStop = false;
        bDelete = false;
    }

    @Override
    protected void _stop() {
        // TODO Auto-generated method stub
        bStop = true;
    }

    @Override
    protected void _delete() {
        // TODO Auto-generated method stub
        bDelete = true;
    }

    @Override
    protected void _release() {
        // TODO Auto-generated method stub
        try {
            if (input != null)
                input.close();
            if (oSavedFile != null)
                oSavedFile.close();
            if (httpUrl != null)
                httpUrl.disconnect();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    protected void _do_delete() {
        // TODO Auto-generated method stub
        if (tempfile != null) {
            File df = new File(tempfile);
            if (df.exists())
                df.delete();
        }
        if (savefile != null) {
            File df = new File(savefile);
            if (df.exists()) {
                df.delete();
            }
        } else {
            DownloadTask task = getDownloadTask();
            if (task == null)
                return;
            savefile = task.savepath + "/" + task.filename;
            if (savefile != null) {
                File df = new File(savefile);
                if (df.exists()) {
                    df.delete();
                }
            }
        }
    }

    @Override
    protected String _getSaveFilePath() {
        return savefile;
    }
}
